<body></body>
<script>
  let activeEffect = null
  const effectStack = [] //effect栈
  const bucket = new WeakMap()

  // 1、原始数据
  const data = {
    foo: 1
  }

  const obj = new Proxy(data, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, newVal) {
      target[key] = newVal
      trigger(target, key)
    }
  })

  function track(target, key) {
    if (!activeEffect) return
    let depMap = bucket.get(target)
    if (!depMap) {
      bucket.set(target, (depMap = new Map()))
    }

    let deps = depMap.get(key)
    if (!deps) {
      depMap.set(key, (deps = new Set()))
    }
    const effectSet = deps.add(activeEffect)
    activeEffect.deps.push(effectSet)
  }

  function trigger(target, key) {
    let depMap = bucket.get(target)
    if (!depMap) return
    const depsSet = depMap.get(key)
    const effectsToRun = new Set(depsSet)
    effectsToRun &&
      effectsToRun.forEach(effectFn => {
        // 5、如果一个副作用函数存在调度器，则调用该调度器，并将副作用函数作为参数传递
        if (effectFn.options.scheduler) {
          //新增
          effectFn.options.scheduler(effectFn) //新增
        } else {
          effectFn()
        }
      })
  }

  // 3、新增代码，收集 options
  function effect(fn, options) {
    // 这里的 effectFn是一个独立函数，就是每一次effect开辟一个新的空间，使用的是新的指针
    function effectFn() {
      cleanup(effectFn)
      //当调用 effect 注册副作用函数时，将副作用函数复制给 activeEffect
      activeEffect = effectFn //注册 effectFn 内存指针, 用于 deps.add(activeEffect)

      // 在调用副作用函数之前将当前副作用压入栈中
      effectStack.push(effectFn)
      fn()
      // 在当前副作用执行完毕后，将当前副作用函数弹出栈，并把 activeEffect 还原为之前的值
      effectStack.pop(effectFn)
      activeEffect = effectStack[effectStack.length - 1]
    }
    // 将options 挂载到 effectFn 上
    effectFn.options = options //4、新增，加一个引用 options
    effectFn.deps = [] //反向收集 effectSet的引用
    // 执行副作用函数
    effectFn()
  }
  function cleanup(effectFn) {
    effectFn.deps.forEach(depSet => {
      depSet.delete(effectFn) //遍历所有的 Set集合删除同一个副作用
    })
    effectFn.deps.length = 0
  }

  // 2、加一个配置，返回副作用函数
  effect(
    () => {
      console.log(obj.foo)
    },
    // 新增options
    {
      // 调度器 scheduler 是一个函数
      scheduler(fn) {
        setTimeout(() => fn())
        setTimeout(() => fn())
        setTimeout(() => fn())
      }
    }
  )

  obj.foo++
  console.log('结束了')

  /*
  分析：
  1、foo的 get执行副作用打印（1）（默认执行了 fn），绑定上 options（包括绑定了scheduler函数）
  2、修改 foo，触发判断，存在 scheduler函数，执行 scheduler()，异步执行 foo的effect
  3、执行是 console.log(obj.foo)， console.log('结束了')，setTimeout(() => console.log(obj.foo))
  */
</script>
